home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / X11 / wais / ir / irsearch.c < prev    next >
C/C++ Source or Header  |  1995-05-09  |  24KB  |  842 lines

  1. /* WIDE AREA INFORMATION SERVER SOFTWARE
  2.    No guarantees or restrictions.  See the readme file for the full standard
  3.    disclaimer.    
  4.    Brewster@think.com
  5. */
  6.  
  7. /* Looks up words in the inverted file index.
  8.  *
  9.  * Important functions:
  10.  * run_search
  11.  * search_for_words
  12.  *
  13.  * to do:
  14.  *    Handle searches on multiple databases
  15.  */
  16.  
  17. /* Change Log:
  18.  * $Log:    irsearch.c,v $
  19.  * Revision 1.54  92/05/10  14:44:35  jonathan
  20.  * Made a little safer on NULL docid's when parsing.
  21.  * 
  22.  * Revision 1.53  92/05/04  17:20:11  jonathan
  23.  * Added test for parsing docids (if null, log error).
  24.  * 
  25.  * Revision 1.52  92/04/29  08:22:17  shen
  26.  * declare global variable "_BE_normalized" to allow turning on/off FE score
  27.  * normalization.
  28.  * 
  29.  * Revision 1.51  92/04/28  16:56:30  morris
  30.  * added boolean to serial engine
  31.  * 
  32.  * Revision 1.50  92/04/01  17:10:21  jonathan
  33.  * ?
  34.  * 
  35.  * Revision 1.49  92/03/23  13:26:27  shen
  36.  * add timing for query. Compile with GET_QUERY_TIMING. print timing every 200 queries.
  37.  * 
  38.  * Revision 1.48  92/03/18  08:56:00  jonathan
  39.  * Removed databaseName argument to getDocumentText and getData.
  40.  * 
  41.  * Revision 1.47  92/02/17  16:22:42  jonathan
  42.  * Added WCAT to types that can be used for relevance feedback.
  43.  * 
  44.  * Revision 1.46  92/02/16  18:04:38  jonathan
  45.  * Demoted more WLOG_ERROR's to WLOG_WARNING's
  46.  * 
  47.  * Revision 1.45  92/02/16  09:51:12  jonathan
  48.  * Plugged some memory leaks.  I be there are more.
  49.  * 
  50.  * Revision 1.44  92/02/15  19:41:20  jonathan
  51.  * Improved logging for invalid relevant documents.
  52.  * 
  53.  * Revision 1.43  92/02/14  16:06:48  jonathan
  54.  * Added diagnostic record for invalid relevant document.
  55.  * 
  56.  * Revision 1.42  92/02/12  17:30:20  jonathan
  57.  * Conditionalized inclusion of object code.
  58.  * 
  59.  * Revision 1.41  92/02/12  17:04:03  jonathan
  60.  * Moved logging info around.
  61.  * 
  62.  * Revision 1.40  92/02/12  15:26:35  morris
  63.  * only call fnished_search_word when the preceeding search was successful
  64.  * 
  65.  * Revision 1.39  92/02/12  13:30:39  jonathan
  66.  * Added "$Log" so RCS will put the log message in the header
  67.  * 
  68.  * changes 5.2.90 HWM
  69.     - changed calls to perror() to calls to panic()
  70.     - made print_best_hits() only print hits w/ non-zero weight
  71.     - made random arrays static instead of reading them in.  
  72.       removed getRandomArray.
  73.     - removed unused variables
  74.   Brewster 7/90 made look_up_word_in_dictionary safer.
  75.   Brewster 7/90 elimiated trailing <lf> on filename and headline table accesses
  76.   HWM 7.12.90 - replaced all calls to panic with error code returns and a log
  77.                 file  
  78.           - added the routine initSearchEngine() which should be called 
  79.             before any other search routine
  80.           - added beFriendly() to give other processes time under 
  81.             multifinder
  82.   JG 5.31.91 - added relevance feedback for line fragments.
  83.   JG 7.8.91  - added doc_id to search_for_words, removed scale_scores.
  84. */
  85.  
  86. #if 0
  87. #define GET_QUERY_TIMING
  88. #endif
  89.  
  90. #define _search_c
  91.  
  92. #include <ctype.h>
  93.  
  94. #include <string.h>     /* for strlen() */
  95. #ifdef THINK_C
  96. #include <unix.h>         /* for sleep() */
  97. #endif /* think_c */
  98.  
  99. #include "cutil.h"
  100. #include "irfiles.h"
  101. #include "irtfiles.h" /* for map_over_words */
  102. #include "irlex.h"
  103. #include "irext.h"
  104. #include "irsearch.h"
  105. #include "docid.h"
  106. #include <math.h>
  107. #include "irretrvl.h"
  108. #ifdef BOOL
  109. #include "irparse.h"
  110. #endif
  111. #include "trie.h"
  112.  
  113. #define TEST_SEARCH     false    /* set to TRUE to allow printing to console */
  114.  
  115. char *server_name = NULL;
  116. long tcp_port = 0;
  117.  
  118. long _BE_normalized = 0;
  119.  
  120. #ifdef GET_QUERY_TIMING
  121. #include <sys/timeb.h>
  122. static struct timeb  s_time, e_time;
  123. static float t_time = 0;
  124. static long n_query = 0;
  125. #endif
  126.  
  127.  
  128. /*----------------------------------------------------------------------*/
  129.  
  130. static Boolean calcDocLength _AP((hit* theHit,long* lines,long* bytes));
  131.  
  132. static Boolean
  133. calcDocLength(theHit,lines,bytes)
  134. hit* theHit;
  135. long* lines;
  136. long* bytes;
  137. /* Given a hit, open the file and figure out how many bytes and lines
  138.    it contains.  This is not needed by the serial search engine (it
  139.    stores these values in its dictionary.  It is used by the dynamic
  140.    help facility).
  141. */
  142. {
  143.   *lines = theHit->number_of_lines;
  144.  
  145.   /* find the length of the document */
  146.   if(theHit->end_character != 0)
  147.     {
  148.       /* document is not whole file, so size is stored */
  149.       *bytes = theHit->end_character - theHit->start_character;
  150.       return(true);
  151.     }
  152.   else
  153.     {    
  154.       /* whole file, find file length from the file */
  155.       FILE* file = NULL;
  156.       if (((file = s_fopen(theHit->filename, "r")) != NULL) &&
  157.       (s_fseek(file, 0L, SEEK_END) == 0)  &&
  158.       ((*bytes = ftell(file)) != -1))
  159.     { s_fclose(file);
  160.       return(true);        /* we are done, bytes is set */
  161.     }
  162.       else
  163.     { s_fclose(file);
  164.       return(false);    /* something went wrong with the file */
  165.     }
  166.     }
  167. }
  168.  
  169. static long search_word_before_pairs _AP((char *word, long char_pos,
  170.                        long line_pos, long weight,
  171.                        long doc_id, time_t date,
  172.                        boolean capitalized, database* db));
  173.  
  174. /* returns 0 is successful, non-0 if error.  A copy of add_word_before_pairs */
  175. static long search_word_before_pairs (word, char_pos, line_pos,
  176.                       weight, doc_id, date, capitalized, db)
  177.      char *word;    /* the word to be indexed, this could be a
  178.                word pair. If NULL there are no more words
  179.                to be indexed */
  180.      long char_pos;    /* the position of the start of the
  181.                word */
  182.      long line_pos;    /* this is passed for the best
  183.                section calculation */
  184.      long weight;    /* how important the word looks
  185.                syntactically (such as is it bold)
  186.                NOT used by signature system */
  187.      long doc_id;     /* current document, this will never be 0 */
  188.      time_t date; /* display day of this document, 0 if not known */
  189.      boolean capitalized; /* if the word started with a cap */
  190.      database* db; /* database to insert the document */
  191. {
  192.   static char last_word[MAX_WORD_LENGTH + 1];
  193.   static long last_doc_id = -1;
  194.   /* The way it works is it remembers if the last word if it was
  195.      capitalized (if not it clears the saved word).  
  196.      If another capitalized word comes along next
  197.      (and it is in the same document), then it makes a joint word and calls 
  198.      add_word with it. */
  199.   if(capitalized){
  200.     if(last_word[0] != '\0' && last_doc_id == doc_id){
  201.       search_word(make_joint_word(last_word, word), 
  202.           char_pos, line_pos, weight, doc_id, 1L, db);
  203.     }
  204.     else{
  205.       last_word[0] = '\0';
  206.     }
  207.     strncpy(last_word, word, MAX_WORD_LENGTH);
  208.     last_doc_id = doc_id;
  209.   }
  210.   else{ /* not capitalized */
  211.     last_word[0] = '\0';
  212.   }
  213.   return(search_word(word, char_pos, 
  214.              line_pos, weight, doc_id, 0L, db));
  215. }
  216.  
  217. long count_trie_words;
  218. long count_uniq;
  219.  
  220. boolean prepare_word_list(words,set,alloc)
  221.      char* words;
  222.      trie* set;
  223.      trie_allocator* alloc;
  224. {
  225.   char* word = NULL;
  226.   int * datum;
  227.   count_trie_words = count_uniq = 0;
  228.   /* printf("words: %s\n", words); */
  229. /*
  230.   word = strtokf(words,wordDelimiter);
  231. */
  232.   word = (char*)strtokf_isalnum(words);
  233.   while(word != NULL){
  234.     long dictionary_value;
  235.     /* trim the string if necessary */
  236.     if(strlen(word) > MAX_WORD_LENGTH){
  237.       word[MAX_WORD_LENGTH] = '\0';
  238.     }
  239.     if(!encode((unsigned char*)word)) {
  240.       panic("can't encode word %s",word);
  241.     }
  242.     datum = (int *)trie_lookup(word,set,alloc);
  243.     if(!datum) {
  244.       panic("trie_lookup failed !!!");
  245.     }
  246.  
  247.       count_trie_words++;
  248.  
  249.     *datum += 1;
  250.  
  251.       if (*datum == 1 ) {
  252.       count_uniq++;
  253.       }
  254.     word = (char *)strtokf_isalnum(NULL);
  255.     beFriendly();
  256.   } 
  257.  
  258.   waislog(WLOG_LOW, WLOG_INFO,
  259.       "after preparing word list, %d search items were presented.",
  260.       count_trie_words);
  261.   waislog(WLOG_LOW, WLOG_INFO, 
  262.       "There are %d words to search for.",
  263.       count_uniq);
  264.  
  265.   return(true);
  266. }
  267.  
  268. boolean search_for_trie_words(dict,db,prefix,docid,result)
  269. trie* dict;
  270. database* db;
  271. char* prefix;
  272. long docid;
  273. boolean result;
  274. {
  275.   char buffer[MAX_WORD_LENGTH+1];
  276.   char tmp_word[MAX_WORD_LENGTH+1];
  277.   char* word;
  278.   long dictionary_value;
  279.   int weight;
  280.   char* tmp=word;
  281.   if (dict == NULL) {
  282.     return result;
  283.   }
  284.   if (*dict->string) {
  285.     strcpy(buffer,prefix);
  286.     strcat(buffer,dict->string);
  287.     word = buffer;
  288.   } else {
  289.     word = prefix;
  290.   }
  291.  
  292.   if (dict->datum) {
  293.     long number_of_occurrences;
  294.     /* this node has data */
  295.     strcpy(tmp_word,word);
  296.     decode(tmp_word);
  297.     result |= search_word(tmp_word,0L,0L,1L,docid,0L,db);
  298.   }
  299.   if (dict->table) {
  300.     int i;
  301.     int len;
  302.     len = strlen(word);
  303.     for (i=0;i<ALPHA_SIZE;i++) {
  304.       if(dict->table[i]) {
  305.     word[len]=(char)i;
  306.     word[len+1]='\0';
  307.     result = search_for_trie_words(dict->table[i],db,word,docid,result);
  308.       }
  309.     }
  310.   }
  311.   return result;
  312. }
  313.  
  314. boolean search_for_words(words, db, doc_id)
  315.      char* words;
  316.      /* break the string into words (using map_over_words)
  317.     and repeatedly call search_word_before_pairs(). 
  318.     Returns true if successful.
  319.     */
  320.      database *db;
  321.      long doc_id;
  322. {
  323.  
  324. #ifdef BOOL
  325.   /* LISP QUERY */
  326.   if( words[0] == '(' ){ /* then it is a lisp query */
  327.     /* this is a temporary stub for the real work */
  328.     char error_string[ERROR_STRING_LEN];
  329.     object* query = (object*)parseQuery(words,QUERY_SYNTAX_LISP,error_string);
  330.     if(query == NULL){
  331.       waislog(WLOG_HIGH, WLOG_ERROR, "Unparsable query %s", error_string);
  332.       return(false);
  333.     }
  334.     else{
  335.       query = (object*)send(query,Evaluate,db);
  336.       return(true);
  337.     }
  338.   }
  339. #endif  /* def BOOL */
  340.  
  341.   /* NORMAL QUERY */
  342.   if( -1 == map_over_words(words, doc_id, 1L, 0L, NULL, NULL, db, 
  343.                (wordfunc*)search_word_before_pairs, 0L, 0L))
  344.     return(false);
  345.   else
  346.     return(true);
  347. }
  348.  
  349. /* gets the next best hit from the search engine and fills in all the slots.
  350.    If the document does not exist, then it gets another, etc.
  351.    It returns 0 if successful */   
  352. long next_best_hit(the_best_hit, db)
  353.      hit *the_best_hit;
  354.      database *db;
  355. {
  356.   document_table_entry doc_entry;
  357.   long ret_value;
  358.   while(1){ /* keep going until we get a good document */
  359.     if(0 != (ret_value = best_hit(db,&(the_best_hit->document_id),
  360.                   &(the_best_hit->best_character),
  361.                   &(the_best_hit->best_line),
  362.                   &(the_best_hit->weight))))
  363.       return(ret_value);
  364.     if(the_best_hit->weight <= 0)    /* if we are out of good stuff, return */
  365.       return(1);
  366.     /* fill in the rest of the hit */
  367.     if (read_document_table_entry(&doc_entry,
  368.                   the_best_hit->document_id,
  369.                   db) 
  370.     == true){
  371.       the_best_hit->start_character = doc_entry.start_character;
  372.       the_best_hit->end_character = doc_entry.end_character;
  373.       the_best_hit->document_length = doc_entry.document_length;
  374.       the_best_hit->number_of_lines = doc_entry.number_of_lines;
  375.       sprintf(the_best_hit->date, "%d", doc_entry.date);
  376.       read_filename_table_entry(doc_entry.filename_id, 
  377.                 the_best_hit->filename,
  378.                 the_best_hit->type,
  379.                 NULL,
  380.                 db),
  381.       strncpy(the_best_hit->headline, 
  382.           read_headline_table_entry(doc_entry.headline_id,db),
  383.           MAX_HEADLINE_LEN);
  384.       if(probe_file_possibly_compressed(the_best_hit->filename))
  385.     return(0);  /* we win */
  386.       else { /* we lose */
  387.     waislog(WLOG_HIGH, WLOG_WARNING, 
  388.         "Dangling File %s in database %s.", 
  389.         the_best_hit->filename,
  390.         db->database_file);
  391.       }
  392.     }
  393.     else {
  394.       waislog(WLOG_HIGH, WLOG_ERROR, 
  395.           "Error reading doc_table_entry for database %s, docid: %ld",
  396.           db->database_file,
  397.           the_best_hit->document_id);
  398.     }
  399.     beFriendly();
  400.   }
  401. }
  402.  
  403. /*----------------------------------------------------------------------*/
  404.  
  405. /* this function figures out if the request is for a NEXT or Previous document.
  406.    If it is, then it makes a header for it and returns it.  If not, then it 
  407.    returns NULL. */
  408.  
  409. WAISDocumentHeader*
  410. handle_next_and_previous(docs, db, waisProtocolVersion, server)
  411. DocObj** docs;
  412. database* db;
  413. long waisProtocolVersion;
  414. char* server;
  415. {
  416.   char* dbName = db->database_file;
  417.   WAISDocumentHeader* header;
  418.   DocID* theDocID = NULL;
  419.   char *local_id;
  420.  
  421.   if(docs != NULL) { /* All of this is for WAIS_Prev and WAIS_next */
  422.     if(docs[0] != NULL && docs[0]->Type != NULL) {
  423.       long id = -1;
  424.  
  425.       if((theDocID = docIDFromAny(docs[0]->DocumentID)) == NULL) {
  426.     waislog(WLOG_HIGH, WLOG_WARNING, "can't parse docid");
  427.     return(NULL);
  428.       }
  429.  
  430.       local_id = anyToString(GetLocalID(theDocID));
  431.  
  432.       if(strcmp(docs[0]->Type,"WAIS_NEXT") == 0)
  433.     id = next_docid(local_id,db);
  434.       else if(strcmp(docs[0]->Type,"WAIS_PREV") == 0)
  435.     id = previous_docid(local_id, db);
  436.  
  437.       freeDocID(theDocID); s_free(local_id);
  438.  
  439.       if (id > -1) {
  440.     document_table_entry doc_entry;
  441.     hit foo;
  442.     long lines,length;
  443.     char local_id[MAX_FILENAME_LEN + 60]; /* filename, start, end */
  444.  
  445.     local_id[0] = '\0';
  446.  
  447.     if (read_document_table_entry(&doc_entry, id, db) == true) {
  448.       foo.start_character = doc_entry.start_character;
  449.       foo.end_character = doc_entry.end_character;
  450.       foo.document_length = doc_entry.document_length;
  451.       foo.number_of_lines = doc_entry.number_of_lines;
  452.  
  453.       read_filename_table_entry(doc_entry.filename_id, 
  454.                     foo.filename,
  455.                     foo.type,
  456.                     NULL,
  457.                     db),
  458.       strncpy(foo.headline, 
  459.           read_headline_table_entry(doc_entry.headline_id,db),
  460.           MAX_HEADLINE_LEN);
  461.       sprintf(foo.date, "%d", doc_entry.date);
  462.       sprintf(local_id, "%ld %ld %s", 
  463.           doc_entry.start_character,
  464.           doc_entry.end_character,
  465.           foo.filename);
  466.         
  467.       if(calcDocLength(&(foo),&lines,&length)){
  468.         /* this document is good, return it */
  469.         char** type = NULL;
  470.         
  471.         if (waisProtocolVersion >= '2'){
  472.           type = (char**)s_malloc((size_t)(sizeof(char*) * 2));
  473.           type[0] = s_strdup(foo.type);
  474.           type[1] = NULL;
  475.         }
  476.         else
  477.           type = NULL;
  478.            
  479.         theDocID = makeDocID();
  480.  
  481.         theDocID->distributorServer = stringToAny(server); 
  482.         theDocID->originalServer = stringToAny(server);    
  483.         theDocID->distributorDatabase = stringToAny(dbName);
  484.         theDocID->originalDatabase = stringToAny(dbName);
  485.         theDocID->distributorLocalID = stringToAny(local_id);
  486.         theDocID->originalLocalID = stringToAny(local_id);
  487.  
  488.         header=
  489.           makeWAISDocumentHeader(anyFromDocID(theDocID),
  490.                      UNUSED,
  491.                      -1L,
  492.                      UNUSED,
  493.                      length,lines,
  494.                      type,
  495.                      s_strdup(dbName),
  496.                      s_strdup(foo.date),
  497.                      s_strdup(foo.headline),
  498.                      NULL);
  499.         freeDocID(theDocID);
  500.         return(header);
  501.       }
  502.       else{ 
  503.         waislog(WLOG_HIGH, WLOG_WARNING, 
  504.             "document <%ld %ld %s> skipped.",
  505.             doc_entry.start_character,
  506.             doc_entry.end_character,
  507.             foo.filename);
  508.         return(NULL);
  509.       }
  510.     }
  511.       }
  512.     }
  513.   }
  514.   return(NULL);
  515. }
  516.  
  517. /*----------------------------------------------------------------------*/
  518. /* search for each of the words in a document, up to a limit.
  519.    this is for relevance feedback. */
  520.  
  521. #define MAX_TEXT_SIZE 100000    /* Maximume size of relevant text */
  522.  
  523. /* returns true if it added the words, false otherwise (not necessarily 
  524.    an error) */
  525. boolean search_for_words_in_document(doc, docid, db, diags, num_diags)
  526. DocObj* doc;
  527. long docid;
  528. database* db;
  529. diagnosticRecord*** diags;  /* list of diagnostics */
  530. long *num_diags;
  531. {
  532.   char * dbName = db->database_file;
  533.   long errorCode;
  534.   WAISDocumentText* doctext;
  535.  
  536.   char prefix[MAX_WORD_LENGTH+1];
  537.   trie *the_dict;
  538.   trie_allocator* alloc;
  539.   count_trie_words =0;
  540.   count_uniq=0;
  541.     
  542.   alloc=make_trie_allocator();
  543.   the_dict = new_trie("",alloc);
  544.   *prefix = 0;
  545.  
  546.   if(doc->Type == NULL ||
  547.      substrcmp(doc->Type,"TEXT") ||
  548.      strcmp(doc->Type,"WSRC") == 0 ||
  549.      strcmp(doc->Type,"WCAT") == 0 ||
  550.      doc->Type[0] == 0) {
  551.  
  552.     doctext = NULL;
  553.     if (doc->ChunkCode == CT_line)
  554.       doctext = getDocumentText(doc, &errorCode, NULL);
  555.     else if ((doc->ChunkCode == CT_byte) ||
  556.          (doc->ChunkCode == CT_document))
  557.       doctext = getData(doc, &errorCode, NULL);
  558.     if (doctext != NULL) {
  559.  
  560.       boolean search_result;
  561.  
  562.       if(doctext->DocumentText->size > MAX_TEXT_SIZE)
  563.     doctext->DocumentText->bytes[MAX_TEXT_SIZE] = 0;
  564.       search_result = prepare_word_list(doctext->DocumentText->bytes,the_dict,alloc);  
  565.       search_result |= search_for_trie_words(the_dict,db,prefix,docid,search_result);
  566.       dispose_trie_allocator(alloc);
  567.  
  568.       freeWAISDocumentText(doctext);
  569.       return(search_result);
  570.     }
  571.     else { /* bad docid? */
  572.       DocID* theDocID = NULL;
  573.       char* local_id = NULL;
  574.       diagnosticRecord* diag = NULL;
  575.       char msg[MAX_FILENAME_LEN * 2];
  576.  
  577.       theDocID = docIDFromAny(doc->DocumentID);
  578.       
  579.       if(theDocID == NULL) {
  580.     local_id = s_strdup("can't parse docid");
  581.       }
  582.       else {
  583.     local_id = anyToString(GetLocalID(theDocID));
  584.   
  585.     freeDocID(theDocID);
  586.       }
  587.       waislog(WLOG_HIGH, WLOG_WARNING,
  588.           "Relevance Feedback with invalid doc-id: '%s'",
  589.           local_id);
  590.       strncpy(msg,"Relevant Document not available: ",
  591.           MAX_FILENAME_LEN);
  592.       s_strncat(msg,local_id,MAX_FILENAME_LEN,MAX_FILENAME_LEN);
  593.       s_free(local_id);
  594.       (*num_diags)++;
  595.       diag = makeDiag(true,D_TemporarySystemError,msg);
  596.       *diags = (diagnosticRecord**)s_realloc(*diags,(size_t)(sizeof(diagnosticRecord*) * *num_diags));
  597.       (*diags)[(*num_diags)-1] = diag;
  598.     }
  599.  
  600.   }
  601.   return(false);
  602. }
  603.  
  604.  
  605. /*----------------------------------------------------------------------*/
  606.  
  607. WAISDocumentHeader*
  608. best_hit_to_header(best_hit, maxRawScore, waisProtocolVersion, server, db)
  609. hit* best_hit;
  610. long maxRawScore;
  611. long waisProtocolVersion;
  612. char *server;
  613. database* db;
  614. {
  615.   long lines,length;
  616.   DocID* theDocID = NULL;
  617.   WAISDocumentHeader* header;
  618.   char* originName = db->database_file;
  619.   char local_id[MAX_FILENAME_LEN + 60]; /* filename, start, end */
  620.   local_id[0] = '\0';
  621.  
  622.   if (true == calcDocLength(best_hit,&lines,&length))
  623.     {                /* this document is good, return it */
  624.       char** type = NULL;
  625.       long normalScore;
  626.       if ( _BE_normalized )
  627.          normalScore = best_hit->weight;
  628.       else {
  629.          normalScore = (long)floor(
  630.                 (((double)best_hit->weight) /
  631.                  ((double)maxRawScore)) *    
  632.                 (MAX_NORMAL_SCORE + 1));
  633.       
  634.         if (normalScore > MAX_NORMAL_SCORE)
  635.          normalScore = MAX_NORMAL_SCORE;
  636.       }
  637.  
  638.       sprintf(local_id, "%ld %ld %s", 
  639.           best_hit->start_character,
  640.           best_hit->end_character,
  641.           best_hit->filename);
  642.          
  643.       if (waisProtocolVersion >= '2') {
  644.     type = (char**)s_malloc((size_t)(sizeof(char*) * 2));
  645.     type[0] = s_strdup(best_hit->type);
  646.     type[1] = NULL;
  647.       }
  648.       else
  649.     type = NULL;
  650.       /*
  651.     printf("header %ld out of %ld\n", *headerNum, 
  652.     wais_search->MaxDocumentsRetrieved); 
  653.     */
  654.       theDocID = makeDocID();
  655.  
  656.       theDocID->distributorServer = stringToAny(server);
  657.       theDocID->originalServer = stringToAny(server);
  658.           
  659.       theDocID->distributorDatabase = stringToAny(originName);
  660.       theDocID->originalDatabase = stringToAny(originName);
  661.           
  662.       theDocID->distributorLocalID = stringToAny(local_id);
  663.       theDocID->originalLocalID = stringToAny(local_id);
  664.  
  665.       header =
  666.     makeWAISDocumentHeader(anyFromDocID(theDocID),
  667.                    UNUSED,
  668.                    (long)normalScore,
  669.                    best_hit->best_line,
  670.                    length,lines,
  671.                    type,
  672.                    s_strdup(originName),
  673.                    s_strdup(best_hit->date),
  674.                    s_strdup(best_hit->headline),
  675.                    NULL);
  676.       freeDocID(theDocID);
  677.       return(header);
  678.     }
  679.   else
  680.     { 
  681.       waislog(WLOG_HIGH, WLOG_WARNING, 
  682.           "document <%ld %ld %s> skipped.",
  683.           best_hit->start_character,
  684.           best_hit->end_character,
  685.           best_hit->filename);
  686.       return(NULL);
  687.     }
  688. }
  689.  
  690.  
  691.  
  692. /*----------------------------------------------------------------------*/
  693.  
  694. boolean run_search(aSearch, headers, diags, index_directory, 
  695.            seed_words_used, waisProtocolVersion, headerNum)
  696. SearchAPDU* aSearch;
  697. WAISDocumentHeader** headers; /* list of results */
  698. diagnosticRecord*** diags;  /* list of diagnostics */
  699. char *index_directory;
  700. char **seed_words_used;  /* called with enough space */
  701. long waisProtocolVersion;
  702. long *headerNum;
  703. /* runs a search on the inverted file index and returns false if it errors 
  704.    in such a way that it can not even make a diagnostic record 
  705.    (should not happen).
  706.    It changes headers with the replies or makes a diagnostic record
  707.  */
  708.   diagnosticRecord* diag = NULL;
  709.   WAISSearch* wais_search = (WAISSearch*)aSearch->Query; /* for convenience */
  710.   database* db = NULL;
  711.   long maxRawScore;
  712.   long i;
  713.   query_parameter_type parameters;
  714.   boolean search_result;
  715.   char server[255];
  716.   WAISDocumentHeader* header;
  717.   long num_diags = 0;
  718.   char dbName[MAX_FILENAME_LEN * 2];
  719.  
  720.   if (aSearch->DatabaseNames == NULL)
  721.     strcpy(dbName,merge_pathnames(INFO_DATABASE_NAME, index_directory));
  722.   else
  723.     strcpy(dbName,merge_pathnames(aSearch->DatabaseNames[0], index_directory));
  724.  
  725. #ifdef GET_QUERY_TIMING
  726.   ftime(&s_time);
  727. #endif
  728.  
  729.   /* strlip .src if it is on the name */
  730.   if(strlen(dbName) > strlen(".src"))
  731.     if(0 == strcmp(dbName + strlen(dbName) - strlen(".src"),
  732.            ".src"))
  733.       dbName[strlen(dbName) - strlen(".src")] = '\0';
  734.   
  735.   if(server_name != NULL)
  736.     sprintf(server, "%s:%d", server_name, tcp_port);
  737.   else
  738.     sprintf(server, "localhost:0");
  739.  
  740.   db = openDatabase(dbName, false, true);
  741.   if (db == NULL){
  742.     char msg[MAX_FILENAME_LEN * 2];
  743.     strncpy(msg,"The following database is not available: ",
  744.         MAX_FILENAME_LEN);
  745.     s_strncat(msg,dbName,MAX_FILENAME_LEN,MAX_FILENAME_LEN);
  746.     diag = makeDiag(false,D_PermanentSystemError,msg);
  747.     *diags = (diagnosticRecord **)s_realloc(*diags,(size_t)(sizeof(diagnosticRecord*) * 2));
  748.     (*diags)[0] = diag;
  749.     (*diags)[1] = NULL;
  750.     return(false);
  751.   }
  752.  
  753.   /* figure out if it is a NEXT or PREVIOUS, if so, return it. */
  754.   header = handle_next_and_previous(wais_search->Docs, db, 
  755.                     waisProtocolVersion, server);
  756.   if(header != NULL){
  757.     headers[(*headerNum)++] = header;
  758.     headers[*headerNum] = NULL;
  759.     return(true);
  760.   }
  761.   
  762.   /* until seed_words_used is supported */
  763.   strcpy(*seed_words_used, wais_search->SeedWords);
  764.  
  765.   parameters.max_hit_retrieved = wais_search->MaxDocumentsRetrieved;
  766.   set_query_parameter(SET_MAX_RETRIEVED_MASK, ¶meters);
  767.  
  768.   search_result = false;
  769.   init_search_word(db);
  770.  
  771. #ifdef RELEVANCE_FEEDBACK
  772.   if(wais_search->Docs != NULL) {
  773.     DocObj* doc = NULL;
  774.     boolean res;
  775.     /* assemble the elements and construct a response */
  776.     for (i = 0, doc = wais_search->Docs[i]; 
  777.      doc != NULL; 
  778.      doc = wais_search->Docs[++i]){
  779.       search_result |= 
  780.     search_for_words_in_document(doc,i+1,db,diags,&num_diags);
  781.     }
  782.     if (*diags != NULL) {
  783.     num_diags++;
  784.     *diags = (diagnosticRecord**)s_realloc(*diags,(size_t)(sizeof(diagnosticRecord*) * num_diags));
  785.     (*diags)[num_diags-1] = NULL;
  786.       }
  787.   }
  788. #endif                /* RELEVANT_FEEDBACK */
  789.  
  790.   search_result |= search_for_words(wais_search->SeedWords, db, 0);
  791.  
  792.   if (search_result == true){ /* the search went ok */
  793.       hit best_hit;
  794.       finished_search_word(db);
  795.       init_best_hit(db);
  796.       for (i = 0; i < wais_search->MaxDocumentsRetrieved; i++){ 
  797.     if(0 != next_best_hit(&best_hit, db))
  798.       break;        /* out of hits */
  799.     if(i == 0)
  800.       maxRawScore = best_hit.weight;
  801.     if (best_hit.weight > 0){
  802.       WAISDocumentHeader* header = 
  803.         best_hit_to_header(&best_hit, maxRawScore,
  804.                    waisProtocolVersion,server,db);
  805.       if(NULL != header){
  806.         headers[(*headerNum)++] = header;
  807.         headers[*headerNum] = NULL;
  808.       }
  809.     }
  810.       }
  811.     }
  812.   else
  813.     {                /* something went awry in the search */
  814.       num_diags++;
  815.       diag = makeDiag(true,D_PermanentSystemError,
  816.               "Serious error in server");
  817.       *diags = (diagnosticRecord**)
  818.     s_realloc(*diags, (size_t)(sizeof(diagnosticRecord*) * num_diags));
  819.       (*diags)[num_diags-2] = diag;
  820.       (*diags)[num_diags-1] = NULL;
  821.     }
  822.   finished_best_hit(db);
  823.   /* free everything */
  824.   closeDatabase(db);
  825. #ifdef GET_QUERY_TIMING
  826.   ftime(&e_time);
  827.   t_time += (e_time.time + e_time.millitm/1000.0) - 
  828.             (s_time.time + s_time.millitm/1000.0);
  829.   n_query++;
  830.   if ( n_query == 200 ) {
  831.    waislog(WLOG_LOW, WLOG_INFO, "searching 200 queries takes %f seconds.",
  832.            t_time);
  833.    waislog(WLOG_LOW, WLOG_INFO, "average %f/query.", t_time/200.0);
  834.    n_query = 0;
  835.    t_time = 0;
  836.    }
  837. #endif
  838.  
  839.   return(true);
  840. }
  841.